﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Common.Particles;
using Common.Utils;
using Common.Exp2D;

namespace Experiments2D.Bublz
{
    class Board2D
    {
        protected int m_rows;
        protected int m_fullRowBubbleNum;
        protected int m_cols;
        protected Vector2 m_position;
        protected Vector2 m_newBubblesPos;
        protected Vector2 m_upperBorderPosition;
        protected int m_oddRowColumnMargin = 0;
        protected int m_rowMargin = - 0;

        private Bubble2D[][] m_board;
        private Bubble2D[] m_newBubblesLine;
        private Rectangle[] m_newRectLine;
        private bool[] m_oddRow;
        private bool m_isNewBubblesRowNumOdd = true;
        private Rectangle[][] m_rectanglesBoard;

        private Random m_random;

        private SpriteFont m_font;
        private long m_score;
        private int m_starNum;       
        private const int m_bubbleScore = 10;
        private int m_level = 1;

        private int m_boardHeight = 0;
        private int m_boardWidth = 0;

        private const int MAX_NO_BULLE_FALL = 4;
        private int m_bubbleNoFall = 0;

        private bool m_ripple = false;
        public bool Ripple
        {
            get { return m_ripple; }
            set { m_ripple = value; }
        }

        private Vector2 m_ripplePos = Vector2.Zero;
        public Vector2 RipplePos
        {
            get { return m_ripplePos; }
        }

        
                
        private Bubble2D m_bubbleSample;
        private Texture2D m_bubbleSampleTexture;

        private Texture2D m_backgroundTexture;
        private Object2D m_background;

        private Texture2D m_borderTexture;

        private Texture2D m_limitTexture;
        private Object2D m_limit;

        private Object2D m_leftBorder;
        private Rectangle m_leftBorderRectangle;


        private Object2D m_rightBorder;
        private Rectangle m_rightBorderRectangle;

        private Rectangle m_lowerBorderRectangle;

        Texture2D m_launcherTexture;
        private Launcher m_launcher;
        public Launcher Launcher
        {
            get { return m_launcher; }
        }

        private Object2D m_cursor;
        public Object2D Cursor
        {
            get { return m_cursor; }
        }
    

        private Texture2D[] m_bubblesTextures;
        private Texture2D[] m_bubblesStarTextures;
        private Texture2D m_deadBubbleTexture;

        private Texture2D m_starTexture;
        private Object2D m_hudStar;
        private Vector2 m_hudStarPosition;

        private Bubble2D m_currentBubble;
        private Vector2 m_currentBubbleDirection;
        private CurrentBubbleState m_currentBubbleState = CurrentBubbleState.Idle;

        private float m_currentBubbleSpeed = 1200;        

        private FallingObjects2D m_fallingBubbles;
        private AimingObject2D m_aimingObjects;
        

        private ContentManager m_contentManager;
        
        private double m_currentTime = 0;
        private CurrentBoardState m_boardState = CurrentBoardState.Idle;
        private int m_moveDownAmount = 0;        
        private int m_currentMoveDown = 0;                
        

        public Board2D(ContentManager contentManager, Vector2 position, int boardHeight, int boardWidth, int rows, int cols, int fullRowNum)
        {   
            m_position = position;
            m_newBubblesPos = position;
            m_upperBorderPosition = m_position;
            m_rows = rows;
            m_cols = cols;
            m_fullRowBubbleNum = fullRowNum;
            m_boardHeight = boardHeight;
            m_boardWidth = boardWidth;

            m_random = new Random();

            m_contentManager = contentManager;

            LoadTextures();

            m_bubbleSample = new Bubble2D(m_bubbleSampleTexture, 0, SpecialBubbleKind.None);
            m_moveDownAmount = m_bubbleSample.Height;
            m_oddRowColumnMargin = m_bubbleSample.Width / 2;
            m_rowMargin = (m_bubbleSample.Texture.Height / 4) * -1;
            

            InitBorders();                        
            InitArrow();
            InitFont();
            
            InitBoard();
            InitLimit();
            InitHud();
            InitBackground();
            InitFallingBubbles();
            InitAimingObjects();
            InitCurrentBubble();
        }

        private void InitHud()
        {
            
        }

        

        private void LoadTextures()
        {
            m_bubbleSampleTexture = m_contentManager.Load<Texture2D>(BublzParams.BubbleBlue);
            m_backgroundTexture = m_contentManager.Load<Texture2D>(BublzParams.BackgroundTexture);
            m_limitTexture = m_contentManager.Load<Texture2D>(BublzParams.LimitTexture);
            m_launcherTexture = m_contentManager.Load<Texture2D>(BublzParams.ArrowTexture);
            m_borderTexture = m_contentManager.Load<Texture2D>(BublzParams.BorderTexture);
            m_starTexture = m_contentManager.Load<Texture2D>(BublzParams.Star);
            m_deadBubbleTexture = m_contentManager.Load<Texture2D>(BublzParams.DeadBubble);

            m_bubblesTextures = new Texture2D[BublzParams.BubblesTextureArray.Length];
            m_bubblesStarTextures = new Texture2D[BublzParams.BubblesTextureArray.Length];            
            for (int i = 0; i < m_bubblesTextures.Length; i++)
            {
                m_bubblesTextures[i] = m_contentManager.Load<Texture2D>(BublzParams.BubblesTextureArray[i]);
                m_bubblesStarTextures[i] = m_contentManager.Load<Texture2D>(BublzParams.BubblesStarTextureArray[i]);
            }
        }

        private void InitLimit()
        {
            Vector2 limitPosition = new Vector2 (m_position.X , (m_fullRowBubbleNum* m_bubbleSample.Height) - (Math.Abs(m_rowMargin) * m_fullRowBubbleNum));            
            m_limit = new Object2D (m_limitTexture, limitPosition ,false);
        }

        private void InitBackground()
        {
            m_background = new Object2D(m_backgroundTexture, m_position, false);
        }      

        private void InitFallingBubbles()
        {
            m_fallingBubbles = new FallingObjects2D ();
        }

        private void InitAimingObjects()
        {
            m_aimingObjects = new AimingObject2D(new Vector2((m_position.X + (m_cols * m_bubbleSample.Width) / 2) - m_bubbleSample.Width / 2, 550 - m_bubbleSample.Height / 2));
            m_hudStarPosition = new Vector2((m_position.X + (m_cols * m_bubbleSample.Width) / 2) - m_bubbleSample.Width / 2, 550 - m_bubbleSample.Height / 2);
        }       

        private void InitCurrentBubble()
        {
            Texture2D bubTexture;
            int kind;
            SpecialBubbleKind bonusKind = SpecialBubbleKind.None;
            bool deadBubble = false;
            GetRandomBubble(out bubTexture, out kind,out bonusKind, out deadBubble);
            m_currentBubble = new Bubble2D(bubTexture, kind, bonusKind);
            ReinitCurrBubbleProperties();
        }

        private void ReinitCurrBubbleProperties()
        {
            //bubble is not draw at it's center origin
            SetInitialCurrBubblePos();
            //Set random color
            SetCurrentBubbleColor();
        }

        private int GetNextBubbleKind()
        {
            //to set next bubble color we must checkall the lower bubbles color                         
            List<int> possibleBubbles = new List<int>();
            for (int i = 0; i < m_cols; i++)
            {
                for (int j = 0; j < m_fullRowBubbleNum ; j++)
                {
                    if ( m_board [j][i] != null)
                    {
                        if (m_board [j][i].Kind != Bubble2D.DeadBubbleKind)
                            possibleBubbles.Add (m_board [j][i].Kind);
                        
                    }
                }
            }
            Texture2D tex;
            int kind;
            SpecialBubbleKind bonusKind;
            bool deadBubble = false;
            for (int i = 0; i < 4; i++)
            {
                GetRandomBubble(out tex, out kind, out bonusKind, out deadBubble);
                possibleBubbles.Add(kind);
            }

            return possibleBubbles[m_random.Next(0, possibleBubbles.Count)];            
        }

        private void SetCurrentBubbleColor()
        {
            Texture2D texture;
            int kind = GetNextBubbleKind();
            texture = m_bubblesTextures[kind];            
            m_currentBubble.Texture = texture;
            m_currentBubble.Kind = kind;
        }

        private void SetInitialCurrBubblePos()
        {
            m_currentBubble.Position = new Vector2((m_position.X + (m_cols * m_bubbleSample.Width) / 2) - m_bubbleSample.Width / 2, 550 - m_bubbleSample.Height / 2);
        }

        private void CreateNewBubblesLine()
        {            
            m_newBubblesLine = new Bubble2D[m_cols];
            m_newRectLine = new Rectangle[m_cols];
            int rowMargin = (m_isNewBubblesRowNumOdd == false) ? 0 : m_oddRowColumnMargin;
            int currentColPos = (int)m_newBubblesPos.X + rowMargin;
            int currentRowPos = (int)m_newBubblesPos.Y - (m_bubbleSample.Height + m_rowMargin);
            Texture2D texture;
            int kind;
            SpecialBubbleKind bonusKind;
            bool deadbubble = false;
            for (int i = 0; i < m_cols - ((m_isNewBubblesRowNumOdd == true) ? 1 : 0); i++)
            {
                Rectangle rect = new Rectangle(currentColPos, currentRowPos , m_bubbleSample.Width, m_bubbleSample.Height);
                m_newRectLine[i] = rect;
                GetRandomBubble(out texture, out kind, out bonusKind, out deadbubble);
                if (deadbubble)
                {
                    texture = m_deadBubbleTexture;
                    kind = Bubble2D.DeadBubbleKind;
                    bonusKind = SpecialBubbleKind.None;
                }
                Bubble2D bubble = new Bubble2D(texture, kind, bonusKind);
                bubble.SetDefaultParams(Color.White, rect.X, rect.Y, Byte.MaxValue);
                m_newBubblesLine [i] = bubble;                    
                currentColPos += m_bubbleSample.Width;                 
            }
            m_isNewBubblesRowNumOdd = !m_isNewBubblesRowNumOdd;            
        }

        private void DrawNewBubbles(GameTime time, SpriteBatch spriteBatch)
        {
            for (int i = 0; i < m_newBubblesLine.Length; i++)
            {
                if (m_newBubblesLine[i] != null)
                    m_newBubblesLine[i].Draw(time, spriteBatch);
            }
        }

        private void MoveNewBubblesDown(int amount)
        {
            //m_newBubblesPos.Y += amount;
            for (int i = 0; i < m_newBubblesLine.Length; i++)
            {
                if (m_newBubblesLine[i] != null)
                {
                    m_newBubblesLine[i].m_position.Y += amount;                    
                }

                if (m_newRectLine[i] != Rectangle.Empty)
                    m_newRectLine[i].Y += amount;
            }
        }

        private void MoveNewBubblesUp(int amount)
        {
            m_newBubblesPos.Y -= amount;
            for (int i = 0; i < m_newBubblesLine.Length; i++)
            {
                if (m_newBubblesLine[i] != null)
                {
                    m_newBubblesLine[i].m_position.Y -= amount;
                }

                if (m_newRectLine[i] != Rectangle.Empty)
                    m_newRectLine[i].Y -= amount;
            }
        }

        private void InitArrow()
        {   
            m_launcher = new Launcher(m_launcherTexture,  new Vector2( (m_position.X + (m_cols * m_bubbleSample.Width) / 2), 550 ));
        }

        private void InitCursor()
        {
            MouseState ms = Mouse.GetState();
            m_cursor = new Object2D(m_contentManager.Load<Texture2D>(BublzParams.CursorTexture), new Vector2(ms.X, ms.Y), true);
        }

        private void InitFont()
        {
            m_font = m_contentManager.Load<SpriteFont>("Font/bublz");
        }

        public void RotateArrowLeft(GameTime time)
        {
            m_launcher.RotateLeft(time);
        }

        public void RotateArrowRight(GameTime time)
        {
            m_launcher.RotateRigth(time);
        }

        public void LaunchCurrentBall()
        {
            if (m_currentBubbleState == CurrentBubbleState.Idle)
            {
                m_currentBubbleDirection = m_launcher.ArrowDirection;
                m_currentBubbleState = CurrentBubbleState.Moving;
            }
        }

        public void LaunchCurrentBall(Vector2 dest)
        {
            if (m_currentBubbleState == CurrentBubbleState.Idle)
            {
                m_currentBubbleDirection = (dest - m_currentBubble.CenterPosition);
                m_currentBubbleDirection.Normalize();
                m_currentBubbleState = CurrentBubbleState.Moving;
            }
        }

        private void InitBorders()
        {   
            Vector2 leftBorderPosition = new Vector2(m_position.X - m_borderTexture.Width , m_position.Y);
            m_leftBorder = new Object2D(m_borderTexture, leftBorderPosition, false);
            m_leftBorderRectangle = new Rectangle((int)leftBorderPosition.X, (int)leftBorderPosition.Y, m_borderTexture.Width, m_borderTexture.Height);

            Vector2 rightBorderPosition = new Vector2(m_position.X + (m_cols * m_bubbleSample.Width), m_position.Y);
            m_rightBorder = new Object2D(m_borderTexture, rightBorderPosition, false);
            m_rightBorderRectangle = new Rectangle((int)rightBorderPosition.X, (int)rightBorderPosition.Y, m_borderTexture.Width, m_borderTexture.Height);

            m_lowerBorderRectangle = new Rectangle((int)m_position.X, m_boardHeight, m_boardWidth, 10);

        }

        private void GetRandomBubble(out Texture2D texture, out int kind, out SpecialBubbleKind bonusKind, out bool deadBubble)
        {
            deadBubble = false;
            LevelManager.Instance.Levels[m_level - 1].GetBubble(out kind, out bonusKind, out deadBubble);            
            if (bonusKind == SpecialBubbleKind.Star)
            {
                texture = m_bubblesStarTextures[kind];                
            }
            else
            {
                texture = m_bubblesTextures[kind];                
            }
        }

        private void InitBoard()
        {
            //init rectangles board
            m_rectanglesBoard = new Rectangle[m_fullRowBubbleNum][];
            //init empty board
            m_board = new Bubble2D[m_fullRowBubbleNum][];            
            //odd row counter
            m_oddRow = new bool[m_fullRowBubbleNum];
            Texture2D texture; int kind;
            SpecialBubbleKind bonusKind;
            int currentRowPos = (int)m_position.Y;
            bool deadBubble;
            //fill board
            for (int i = 0; i < m_fullRowBubbleNum; i++)
            {
                m_rectanglesBoard [i] = new Rectangle [m_cols];
                m_board[i] = new Bubble2D [m_cols];                
                int rowMargin = ((i % 2 ) == 0) ? 0 : m_oddRowColumnMargin;
                int currentColPos = (int)m_position.X + rowMargin;
                m_oddRow[i] = ((i % 2) != 0);
                for (int j = 0; j < m_cols  - (((i % 2) == 0) ? 0 : 1); j++)
                {   
                    Rectangle rect = new Rectangle(currentColPos, currentRowPos, m_bubbleSample.Width, m_bubbleSample.Height);
                    m_rectanglesBoard[i][j] = rect;
                    GetRandomBubble(out texture, out kind, out bonusKind, out deadBubble);
                    Bubble2D bubble = new Bubble2D(texture, kind, bonusKind);
                    bubble.SetDefaultParams(Color.White, rect.X, rect.Y, Byte.MaxValue);                    
                    if (i < m_rows)
                    {                        
                        m_board[i][j] = bubble;
                        m_board[i][j].I = i;
                        m_board[i][j].J = j;                                                
                    }
                    currentColPos += m_bubbleSample.Width;
                }
                currentRowPos += m_bubbleSample.Height + m_rowMargin;
            }

            CreateNewBubblesLine();
        }

        public void KillAllBubbles()
        {
            for (int i = 0; i < m_fullRowBubbleNum; i++)
            {
                for (int j = 0; j < m_cols; j++)
                {
                    if (m_board[i][j] != null)
                    {
                        m_fallingBubbles.AddObject(m_board[i][j]);
                        m_board[i][j] = null;
                    }
                }
            }
        }

        private void MoveBubblesDown(int amount)
        {   
            //m_upperBorderPosition.Y += amount;
            for (int i = 0; i < m_fullRowBubbleNum ; i++)
            {
                for (int j = 0; j < m_cols; j++)
                {                    
                    if (m_board[i][j] != null)
                    {   
                        m_board[i][j].m_position.Y += amount;
                    }
                    m_rectanglesBoard[i][j].Y += amount;                    
                }
            }
        }

        private void MoveBubblesUp(int amount)
        {
            m_upperBorderPosition.Y -= amount;
            for (int i = 0; i < m_fullRowBubbleNum; i++)
            {
                for (int j = 0; j < m_cols; j++)
                {
                    if (m_board[i][j] != null)
                    {
                        Vector2 position = new Vector2(m_board[i][j].Position.X, m_board[i][j].Position.Y - amount);
                        m_board[i][j].Position = position;
                    }
                    m_rectanglesBoard[i][j].Y -= amount;
                }
            }

        }

        public bool LoadLevel()
        {
            return false;
        }

        public void Update(GameTime time)
        {   
            m_currentTime += time.ElapsedGameTime.TotalMilliseconds;
            bool moveDown = false;
            bool levelCount = false;
            int bubbleCount = 0;
            for (int i = 0; i < m_fullRowBubbleNum && !levelCount; i++)
            {
                for (int j = 0; j < m_cols && !levelCount; j++)
                {
                    if (m_board[i][j] != null)
                    {
                        bubbleCount++;
                        if (bubbleCount > LevelManager.Instance.Levels[m_level - 1].MinBubbleCountToMoveDown)
                        {
                            levelCount = true;
                        }
                    }
                }
            }
            moveDown = !levelCount;
            if (m_bubbleNoFall >= MAX_NO_BULLE_FALL)
            {
                moveDown = true;
                m_bubbleNoFall = 0;
            }

            if (m_currentTime > LevelManager.Instance.Levels [m_level -1].BoardMoveInterval || moveDown)
            {
                m_boardState = CurrentBoardState.MovingDown;
            }

            if (m_boardState == CurrentBoardState.MovingDown)
            {
                MoveBubblesDown(1);
                MoveNewBubblesDown(1);
                m_currentMoveDown ++;
                if (m_currentMoveDown > m_moveDownAmount + m_rowMargin)
                {
                    m_boardState = CurrentBoardState.Idle;

                    if (!moveDown) //dont reset current time if moveDown flag is set
                    {
                        m_currentTime = 0;
                    }
                    m_currentMoveDown = 0;

                    for (int i = 0; i < m_cols; i++)
                    {
                        if (m_board[m_rectanglesBoard.Length - 2][i] != null)
                        {
                            m_fallingBubbles.AddObject(m_board[m_rectanglesBoard.Length - 2][i]);
                            m_score--;
                            m_board[m_rectanglesBoard.Length - 2][i] = null;
                        }                        
                    }

                    //Update current rectangle board
                    for (int i = m_rectanglesBoard.Length - 1; i > 0; i--)
                    {
                        for (int j = 0; j < m_rectanglesBoard[i].Length; j++)
                        {
                            m_rectanglesBoard[i][j] = m_rectanglesBoard[i - 1][j];
                        }
                    }

                    for (int i = m_board.Length - 1; i > 0; i--)
                    {
                        for (int j = 0; j < m_board[i].Length; j++)
                        {
                            m_board[i][j] = m_board[i - 1][j];
                            if (m_board[i][j] != null)
                            {
                                m_board[i][j].I = i;
                                m_board[i][j].J = j;
                            }
                        }
                    }

                    //First line
                    for (int i = 0; i < m_newRectLine.Length ; i++)
                    {
                        m_rectanglesBoard [0][i] = m_newRectLine [i];
                    }

                    for (int i = 0; i < m_newBubblesLine.Length; i++)
                    {
                        m_board[0][i] = m_newBubblesLine[i];
                        if (m_board[0][i] != null)
                        {
                            m_board[0][i].I = 0;
                            m_board[0][i].J = i;
                        }
                    }
                    
                    for (int i = 0; i < m_oddRow.Length; i++)
                    {
                        m_oddRow[i] = !m_oddRow [i];
                    }

                    CreateNewBubblesLine();
                }
            }

            //if (m_boardState == CurrentBoardState.MovingUp)
            //{
            //    MoveBubblesUp(1);
            //    MoveNewBubblesUp(1);
            //    m_currentMoveUp++;
            //    if (m_currentMoveUp > m_moveUpAmount)
            //    {
            //        m_boardState = CurrentBoardState.Idle;
            //        m_currentMoveUp = 0;
            //    }
            //}

            if (m_currentBubbleState == CurrentBubbleState.Moving)
            {
                UpdateCurrentBubble(time);                
                CheckCurrentBubbleCollision(time);
                
            }
            UpdateFallingBubbles(time);
            UpdateAimingBubbles(time);
            
        }

        private void UpdateFallingBubbles(GameTime time)
        {
            m_fallingBubbles.Update(time);
        }

        private void UpdateAimingBubbles(GameTime time)
        {
            m_aimingObjects.Update(time);
        }

        private void UpdateCurrentBubble(GameTime time)
        {
            if (m_currentBubbleState == CurrentBubbleState.Moving)
            {
                Vector2 newPosition = GetCurrBubbleNextPosIncrement(time);
                m_currentBubble.Position += newPosition;
            }
        }

        private Vector2 GetCurrBubbleNextPosIncrement(GameTime time)
        {
            return  (m_currentBubbleSpeed * m_currentBubbleDirection * (float)time.ElapsedGameTime.TotalSeconds);            
        }

        private Object2D BubbleToObject(Bubble2D bubble)
        {
            return (Object2D)bubble;
        }

        private void CheckCurrentBubbleCollision(GameTime time)
        {
            CheckBordersCollision();
            CheckUpperBorderCollision();

            Rectangle rect = Rectangle.Empty;
            int rowCurrent = 0,colCurrent = 0;
            int rowNext = 0, colNext = 0;
            int row = 0, col = 0;
            Vector2 nextPosition = m_currentBubble.Position + GetCurrBubbleNextPosIncrement(time);
            Vector2 oldPosition = m_currentBubble.Position - GetCurrBubbleNextPosIncrement(time);
            Rectangle rectCurrent = Rectangle.Empty;
            Rectangle rectNext = Rectangle.Empty;
            bool hitTestCurrent = HitTestBubbleBoardDistance(m_currentBubble.Position, out rectCurrent, out rowCurrent, out colCurrent);
            bool hitTestNext = HitTestBubbleBoardDistance(nextPosition, out rectNext, out rowNext, out colNext);
            if (m_currentBubbleState == CurrentBubbleState.Moving && hitTestCurrent && hitTestNext)
            {
                if (hitTestCurrent )
                {
                    rect = rectCurrent;
                    row = rowCurrent;
                    col = colCurrent;
                    m_currentBubbleState = CurrentBubbleState.Idle;
                }
                else if (hitTestNext)
                {
                    rect = rectNext;
                    row = rowNext;
                    col = colNext;
                    float amount = 0.1f;
                    Vector2 midPosition = Vector2.Lerp(m_currentBubble.Position, nextPosition, amount);
                    while (HitTestBubbleBoardDistance(midPosition, out rect, out row, out col))
                    {
                        amount += 0.1f;
                    }
                    m_currentBubbleState = CurrentBubbleState.Idle;
                    m_currentBubble.m_position.X = midPosition.X;
                    m_currentBubble.m_position.Y = midPosition.Y;
                }

                if (m_currentBubbleState == CurrentBubbleState.Idle) //collision happened
                {
                    List<Rectangle> rectList;
                    List<Point> indicesList;
                    GetEmptyNeighbours(row , col, out rectList, out indicesList);
                    if (rectList.Count == 0 && row < m_fullRowBubbleNum - 1)
                    {
                        GetEmptyNeighbours(row + 1, col, out rectList, out indicesList);
                    }
                    //Max intersect with each rectangle
                    double maxSurface = 0;
                    Rectangle maxRect = Rectangle.Empty;
                    double surface = 0;
                    Point maxIndices = Point.Zero;
                    for (int i = 0; i < rectList.Count; i++)
                    {
                        if (Object2D.IntersectRectangle(m_currentBubble.Rectangle, rectList[i], out surface))
                        {
                            if (surface > maxSurface)
                            {
                                maxRect = rectList[i];
                                maxSurface = surface;
                                maxIndices = indicesList[i];
                            }
                        }
                    }

                    if (maxSurface > 0)
                    {
                        Point p = maxIndices;
                        m_currentBubble.m_position.X = m_rectanglesBoard[p.X][p.Y].X;
                        m_currentBubble.m_position.Y = m_rectanglesBoard[p.X][p.Y].Y;

                        Bubble2D bubble = new Bubble2D(m_currentBubble.Texture , m_currentBubble.Kind, m_currentBubble.BonusKind);
                        bubble.SetDefaultParams(Color.White, m_currentBubble.m_position.X, m_currentBubble.m_position.Y, Byte.MaxValue);
                        m_board[p.X][p.Y] = bubble;
                        bubble.I = p.X;
                        bubble.J = p.Y;
                        CheckSameBubbles(bubble);

                        ReinitCurrBubbleProperties();
                    }
                    else
                    {
                        //Error: corner + bubble collision error
                        //We can put current bubble in the row + 1 rectangle: we have to check
                        //m_currentBubble.m_position                        
                        m_fallingBubbles.AddObject(m_currentBubble);
                        ReinitCurrBubbleProperties();                        
                    }
                }
            }
        }        

        private void CheckSameBubbles(Bubble2D bubble)
        {
            List<Bubble2D> bubbles = GetAllSameNeighbours(bubble);
            bubbles.Add(bubble);
            for (int i = 0; i < bubbles.Count; i++)
            {
                bubbles[i].Visited = false;                
            }

            //if more than 2 remove all bubbles
            int nbBubbles = 0;
            List<Object2D> starBubbles = new List<Object2D>();
            List<Vector2> targetList = new List<Vector2>();
            Vector2 starBubblePosition = Vector2.Zero;
            if (bubbles.Count > 2)
            {
                m_bubbleNoFall = 0;
                nbBubbles = bubbles.Count;
                for (int i = 0; i < bubbles.Count; i++)
                {
                    if (bubbles[i].BonusKind == SpecialBubbleKind.Star)
                    {
                        starBubblePosition = bubbles[i].CenterPosition;
                        starBubbles.Add(new Object2D(m_starTexture, bubbles[i].Position, false));
                        targetList.Add(m_hudStarPosition);
                    }
                }
                m_fallingBubbles.Init(bubbles.ConvertAll<Object2D>(new Converter<Bubble2D, Object2D>(BubbleToObject)));

                //Remove bubbles from board
                for (int i = 0; i < bubbles.Count; i++)
                {
                    m_board[bubbles[i].I][bubbles[i].J] = null;
                }
                //Remove orphan bubbles
                List<Bubble2D> orphBubbles = GetOrphanBubbles();
                for (int i = 0; i < orphBubbles.Count; i++)
                {
                    if (orphBubbles[i].BonusKind == SpecialBubbleKind.Star)
                    {
                        starBubblePosition = orphBubbles[i].CenterPosition;
                        starBubbles.Add(new Object2D(m_starTexture, orphBubbles[i].Position, false));
                        targetList.Add(m_hudStarPosition);
                    }
                }
                nbBubbles += orphBubbles.Count;
                m_fallingBubbles.AddObjectsList(orphBubbles.ConvertAll<Object2D>(new Converter<Bubble2D, Object2D>(BubbleToObject)));
                m_aimingObjects.Init(starBubbles, targetList);
                for (int i = 0; i < orphBubbles.Count; i++)
                {
                    m_board[orphBubbles[i].I][orphBubbles[i].J] = null;
                }

                m_score += nbBubbles;

                if (starBubbles.Count > 0)
                {
                    m_ripple = true;                    
                    m_ripplePos = starBubblePosition;

                    m_starNum += starBubbles.Count;
                    if (m_starNum > LevelManager.Instance.Levels[m_level - 1].StarObjective)
                    {
                        m_level++;
                        m_starNum = 0;
                    }
                 
                }
            }
            else
            {
                m_bubbleNoFall++;
            }
        }

        private List<Bubble2D> GetOrphanBubbles()
        {
            List<Bubble2D> orphanBubbles = new List<Bubble2D>();
            for (int i = 0; i < m_board.Length; i++)
            {
                for (int j = 0; j < m_board[i].Length; j++)
                {
                    if ( m_board [i][j] != null)
                        m_board[i][j].ConnectedToTop = false;
                }
            }
            //Check first line bubbles
            for (int j = 0; j < m_board[0].Length; j++)
            {
                if (m_board[0][j] != null)
                {
                    m_board[0][j].ConnectedToTop = true;
                    CheckForRemoval(m_board [0][j]);
                }
            }

            for (int i = 0; i < m_board.Length; i++)
            {
                for (int j = 0; j < m_board[i].Length; j++)
                {
                    if (m_board [i][j] != null && m_board[i][j].ConnectedToTop == false)
                    {
                        orphanBubbles.Add(m_board[i][j]);
                    }
                }
            }

            return orphanBubbles;
        }

        private void CheckForRemoval(Bubble2D bubble2D)
        {
            List<Bubble2D> bubbles = GetNeighbours(bubble2D);
            for (int i = 0; i < bubbles.Count; i++)
            {
                if (bubbles[i].ConnectedToTop == false)
                {
                    bubbles[i].ConnectedToTop = true;
                    CheckForRemoval(bubbles[i]);
                }
            }
        }

        private void CheckBordersCollision()
        {
            //Check collision with borders
            //1 check collision with right border
            if (m_currentBubble.Rectangle.Intersects(m_rightBorderRectangle))
            {
                if (m_currentBubble.Position.X >= m_rightBorder.Position.X)
                {
                    m_currentBubble.m_position.X = m_rightBorder.Position.X - m_bubbleSample.Width;                    
                }
                //Bounce ball
                m_currentBubbleDirection.X *= -1;
            }

            //2 check collision with left border
            if (m_currentBubble.Rectangle.Intersects(m_leftBorderRectangle))
            {
                if (m_currentBubble.Position.X <= m_leftBorder.Position.X)
                    m_currentBubble.m_position.X = m_leftBorder.Position.X;
                //Bounce ball
                m_currentBubbleDirection.X *= -1;
            }

            //3 check collision with lower boarder
            if (m_currentBubble.Rectangle.Intersects(m_lowerBorderRectangle))
            {
                if (m_currentBubble.Position.Y >= m_lowerBorderRectangle.Y)
                    m_currentBubble.m_position.Y = m_leftBorder.Position.Y;
                //Bounce ball
                m_currentBubbleDirection.Y *= -1;
            }

        }

        private void CheckUpperBorderCollision()
        {
            Rectangle rect = new Rectangle((int)m_upperBorderPosition.X, (int)m_upperBorderPosition.Y, (int)(m_rightBorder.Position.X - m_leftBorder.Position.X), 2);
            if (m_currentBubble.Rectangle.Intersects(rect))
            {
                //Set best bubble position
                Rectangle maxRect = Rectangle.Empty;
                double maxSurface = 0;
                double currSurface;
                Point maxIndice = Point.Zero;
                for (int j = 0; j < m_board[0].Length; j++)
                {
                    if (m_board[0][j] == null && 
                        Object2D.IntersectRectangle(m_currentBubble.Rectangle, m_rectanglesBoard[0][j],out currSurface) && 
                        maxSurface < currSurface)                        
                    {   
                        maxSurface = currSurface;
                        maxRect = m_rectanglesBoard[0][j];
                        maxIndice.X = 0;
                        maxIndice.Y = j;
                    }
                }
                if (maxRect != Rectangle.Empty)
                {
                    Point p = maxIndice;
                    m_currentBubble.m_position.X = m_rectanglesBoard[p.X][p.Y].X;
                    m_currentBubble.m_position.Y = m_rectanglesBoard[p.X][p.Y].Y;

                    Bubble2D bubble = new Bubble2D(m_currentBubble.Texture, m_currentBubble.Kind, m_currentBubble.BonusKind);
                    bubble.SetDefaultParams(Color.White, m_currentBubble.m_position.X, m_currentBubble.m_position.Y, 230);
                    m_board[p.X][p.Y] = bubble;
                    bubble.I = p.X;
                    bubble.J = p.Y;
                    CheckSameBubbles(bubble);

                    ReinitCurrBubbleProperties();
                    m_currentBubbleState = CurrentBubbleState.Idle;
                }
                else
                {
                    ReinitCurrBubbleProperties();
                }

            }

        }

        private List<Bubble2D> GetAllSameNeighbours(Bubble2D bubble)
        {

            List<Bubble2D>list =  GetSameNeighbours(bubble);
            if (list.Count > 0)
            {
                List<Bubble2D> tmpList = new List<Bubble2D>(list);
                foreach (Bubble2D bub in tmpList)
                {
                    list.AddRange(GetAllSameNeighbours(bub));
                    
                }
            }
            return list;
        }

        private List<Bubble2D> GetNeighbours(Bubble2D bubble)
        {
            
            List<Bubble2D> neighboursList = new List<Bubble2D>();
            int i = bubble.I;
            int j = bubble.J;

            // left and right neighbours
            if (j > 0 && m_board[i][j - 1] != null)
                neighboursList.Add(m_board[i][j - 1]);

            if (j < m_board[i].Length - 1 && m_board[i][j + 1] != null)
                neighboursList.Add(m_board[i][j + 1]);


            //upper neighbours
            if (i > 0)
            {
                if (m_oddRow [i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j] != null) //right
                    {
                        neighboursList.Add(m_board[i - 1][j]);
                    }
                    if (j > 0 && m_board[i - 1][j - 1] != null) //left
                    {
                        neighboursList.Add(m_board[i - 1][j - 1]);
                    }
                }
                else // odd row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j + 1] != null)  //right
                    {
                        neighboursList.Add(m_board[i - 1][j + 1]);
                    }
                    //left                    
                    if (j >= 0 && m_board[i - 1][j] != null)
                    {
                        neighboursList.Add(m_board[i - 1][j]);
                    }
                }
            }
            //lower neighbours  
            if (i < m_board.Length - 1)
            {
                if (m_oddRow [i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i + 1][j] != null) //right
                    {
                        neighboursList.Add(m_board[i + 1][j]);
                    }
                    if (j > 0 && m_board[i + 1][j - 1] != null) //left
                    {
                        neighboursList.Add(m_board[i + 1][j - 1]);
                    }
                }
                else // odd row
                {
                    if (j < m_board[i].Length - 1 && m_board[i + 1][j + 1] != null)
                    {
                        neighboursList.Add(m_board[i + 1][j + 1]);
                    }
                    //left                    
                    if (j >= 0 && m_board[i + 1][j] != null)
                    {
                        neighboursList.Add(m_board[i + 1][j]);
                    }
                }
            }            

            return neighboursList;
        }

        private List<Bubble2D> GetSameNeighbours(Bubble2D bubble)
        {
            bubble.Visited = true;
            List<Bubble2D> neighboursList = new List<Bubble2D>();
            int i = bubble.I;
            int j = bubble.J;            

            // left and right neighbours
            if (j > 0 && m_board[i][j - 1] != null && m_board[i][j - 1].Kind == bubble.Kind && m_board[i][j - 1].Visited == false)
                neighboursList.Add(m_board[i][j - 1]);
            
            if (j < m_board[i].Length - 1 && m_board[i][j + 1] != null && m_board[i][j + 1].Kind == bubble.Kind && m_board[i][j + 1].Visited == false)
                neighboursList.Add(m_board[i][j + 1]);
            

            //upper neighbours
            if (i > 0)
            {
                if (m_oddRow [i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j] != null && m_board[i - 1][j].Kind == bubble.Kind && m_board[i - 1][j].Visited == false) //right
                    {
                        neighboursList.Add(m_board[i - 1][j]);
                    }
                    if (j > 0 && m_board[i - 1][j - 1] != null && m_board[i - 1][j - 1].Kind == bubble.Kind && m_board[i - 1][j - 1].Visited == false) //left
                    {
                        neighboursList.Add(m_board[i - 1][j - 1]);
                    }
                }
                else // odd row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j + 1] != null && m_board[i - 1][j + 1].Kind == bubble.Kind && m_board[i - 1][j + 1].Visited == false)  //right
                    {
                        neighboursList.Add(m_board[i - 1][j + 1]);
                    }
                    //left                    
                    if (j >= 0 && m_board[i - 1][j] != null && m_board[i - 1][j].Kind == bubble.Kind && m_board[i - 1][j].Visited == false)
                    {
                        neighboursList.Add(m_board[i - 1][j]);
                }
                }
            }
            //lower neighbours
            if (i < m_board.Length - 1)
            {
                if (m_oddRow [i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i + 1][j] != null && m_board[i + 1][j].Kind == bubble.Kind && m_board[i + 1][j].Visited == false) //right
                    {
                        neighboursList.Add(m_board[i + 1][j]);
                    }
                    if (j > 0 && m_board[i + 1][j - 1] != null && m_board[i + 1][j - 1].Kind == bubble.Kind && m_board[i + 1][j - 1].Visited == false) //left
                    {
                        neighboursList.Add(m_board[i + 1][j - 1]);
                    }
                }
                else // odd row
                {
                    if (j < m_board[i].Length - 1 && m_board[i + 1][j + 1] != null && m_board[i + 1][j + 1].Kind == bubble.Kind && m_board[i + 1][j + 1].Visited == false)
                    {
                        neighboursList.Add(m_board[i + 1][j + 1]);
                    }
                    //left                    
                    if (j >= 0 && m_board[i + 1][j] != null && m_board[i + 1][j].Kind == bubble.Kind && m_board[i + 1][j].Visited == false)
                    {
                        neighboursList.Add(m_board[i + 1][j]);
                    }
                }
            }

            for (int k = 0; k < neighboursList.Count; k++)
                neighboursList[k].Visited = true;

            return neighboursList;
        }

        private List<Rectangle> GetEmptyNeighbours (int i, int j)
        {
            List<Rectangle> neighboursList = new List<Rectangle>();

            // left and right neighbours
            if (j > 0 && m_board[i][j - 1] == null)
                neighboursList.Add(m_rectanglesBoard[i][j - 1]);
            if (j < m_board[i].Length - 1 && m_board[i][j + 1] == null)
                neighboursList.Add(m_rectanglesBoard[i][j + 1]);

            //upper neighbours
            if (i > 0)
            {
                if (m_oddRow [i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j] == null) //right
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j]);
                    }
                    if (j > 0 && m_board[i - 1][j - 1] == null) //left
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j - 1]);
                    }
                }
                else // odd row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j + 1] == null) //right
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j + 1]);
                    }
                    //left                    
                    if (j >= 0 && m_board[i - 1][j] == null)
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j]);
                    }
                }
            }
            //lower neighbours
            if (i < m_board.Length - 1)
            {
                if (m_oddRow [i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i + 1][j] == null) //right
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j]);
                    }
                    if (j > 0 && m_board[i + 1][j - 1] == null) //left
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j - 1]);
                    }
                }
                else // odd row
                {
                    if (j < m_board[i].Length - 1 && m_board[i + 1][j + 1] == null)
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j + 1]);
                    }
                    //left                    
                    if (j >= 0 && m_board[i + 1][j] == null)
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j]);
                    }
                }
            }
            return neighboursList;
        }

        private void GetEmptyNeighbours(int i, int j, out List<Rectangle> neighboursList, out List<Point> indicesList)
        {
            neighboursList = new List<Rectangle>();
            indicesList = new List<Point>();

            // left and right neighbours
            if (j > 0 && m_board[i][j - 1] == null)
            {
                neighboursList.Add(m_rectanglesBoard[i][j - 1]);
                indicesList.Add(new Point(i, j - 1));
            }
            if (j < m_board[i].Length - 1 && m_board[i][j + 1] == null)
            {
                neighboursList.Add(m_rectanglesBoard[i][j + 1]);
                indicesList.Add(new Point(i, j + 1));
            }
            
            //lower neighbours
            if (i < m_board.Length - 1)
            {
                if (m_oddRow [i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i + 1][j] == null) //right
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j]);
                        indicesList.Add(new Point(i + 1, j));
                    }
                    if (j > 0 && m_board[i + 1][j - 1] == null) //left
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j - 1]);
                        indicesList.Add(new Point(i + 1, j - 1));
                    }
                }
                else // odd row
                {
                    if (j < m_board[i].Length - 1 && m_board[i + 1][j + 1] == null)
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j + 1]);
                        indicesList.Add(new Point(i + 1, j + 1));
                    }
                    //left                    
                    if (j >= 0 && m_board[i + 1][j] == null)
                    {
                        neighboursList.Add(m_rectanglesBoard[i + 1][j]);
                        indicesList.Add(new Point(i + 1, j));
                    }
                }
            }

            //upper neighbours
            if (i > 0)
            {
                if (m_oddRow[i] == false)//(i % 2 == 0) // even row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j] == null) //right
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j]);
                        indicesList.Add(new Point(i - 1, j));
                    }
                    if (j > 0 && m_board[i - 1][j - 1] == null) //left
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j - 1]);
                        indicesList.Add(new Point(i - 1, j - 1));
                    }
                }
                else // odd row
                {
                    if (j < m_rectanglesBoard[i].Length - 1 && m_board[i - 1][j + 1] == null) //right
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j + 1]);
                        indicesList.Add(new Point(i - 1, j + 1));
                    }
                    //left                    
                    if (j >= 0 && m_board[i - 1][j] == null)
                    {
                        neighboursList.Add(m_rectanglesBoard[i - 1][j]);
                        indicesList.Add(new Point(i - 1, j));
                    }
                }
            }
            
        }        

        public void Draw(GameTime time, SpriteBatch spriteBatch)
        {
            DrawBackground(time, spriteBatch);

            DrawBorders(time, spriteBatch);

            m_currentBubble.Draw(time, spriteBatch);

            m_launcher.Draw(time, spriteBatch);

            DrawNewBubbles(time, spriteBatch);

            DrawBubbles(time, spriteBatch);

            DrawFallingBubbles(time, spriteBatch);

            DrawAimingObjects(time, spriteBatch);

            DrawLimit(time, spriteBatch);

            DrawHud(time, spriteBatch);
        }

        private void DrawFallingBubbles(GameTime time, SpriteBatch spriteBatch)
        {
            m_fallingBubbles.Draw(time, spriteBatch);
        }

        private void DrawAimingObjects(GameTime time, SpriteBatch spriteBatch)
        {
            m_aimingObjects.Draw(time, spriteBatch);
        }

        private void DrawBubbles(GameTime time, SpriteBatch spriteBatch)
        {
            for (int i = 0; i < m_board.Length; i++)
            {
                for (int j = 0; j < m_board[i].Length; j++)
                {

                    if (m_board[i][j] != null)
                    {
                        m_board[i][j].Draw(time, spriteBatch);
                    }
                }
            }
        }

        private void DrawBorders(GameTime time, SpriteBatch spriteBatch)
        {
            //m_rightBorder.Draw(time, spriteBatch);
            //m_leftBorder.Draw(time, spriteBatch);
        }

        private void DrawLimit(GameTime time, SpriteBatch spriteBatch)
        {
            m_limit.Draw(time, spriteBatch);
        }

        private void DrawBackground(GameTime time, SpriteBatch spriteBatch)
        {
            m_background.Draw(time, spriteBatch);
        }

        private void DrawHud(GameTime time, SpriteBatch spriteBatch)
        {
            string level = "Level " + m_level.ToString();
            Vector2 stringSize =  m_font.MeasureString(level);
            Vector2 stringPos = new Vector2(m_rightBorder.Position.X - stringSize.X, m_limit.Position.Y); 
            spriteBatch.DrawString(m_font, "Score " + m_score.ToString() , m_limit.Position, Color.White);
            spriteBatch.DrawString(m_font, level, stringPos, Color.White);
            spriteBatch.Draw(m_starTexture, new Vector2(m_limit.Position.X, m_limit.Position.Y + stringSize.Y + 2), Color.White);
            spriteBatch.DrawString(m_font , m_starNum.ToString () + " / " +  LevelManager.Instance.Levels [m_level - 1].StarObjective.ToString () , new Vector2(m_limit.Position.X + m_starTexture.Width, m_limit.Position.Y + stringSize.Y + 2 + m_starTexture.Height / 5) , Color.White);             
        }

        public bool HitTestRectangleBoard(Vector2 position, out Rectangle rect, out int row,out int col)
        {
            bool stop = false;
            rect = Rectangle.Empty;
            row = 0;
            col = 0;
            if (position == null)
                return false;
            if (m_rectanglesBoard == null)
                return false;            

            for (int i = 0; i < m_rectanglesBoard.Length && !stop ; i++)
            {
                for (int j = 0; j < m_rectanglesBoard[i].Length && !stop; j++)
                {
                    if (m_rectanglesBoard[i][j] != null && m_rectanglesBoard[i][j].Contains((int)position.X, (int)position.Y) &&  m_board [i][j] == null)
                    {
                        rect = m_rectanglesBoard[i][j];
                        stop = true;
                        row = i;
                        col = j;
                    }
                }
            }
            return stop;            
        }       

        public bool HitTestBubbleBoardDistance(Vector2 position, out Rectangle rect, out int row, out int col)
        {
            bool stop = false;
            rect = Rectangle.Empty;
            row = 0;
            col = 0;
            if (position == null)
                return false;
            if (m_rectanglesBoard == null)
                return false;

            Vector2 centerPosition = new Vector2(position.X + m_bubbleSample.Width / 2, position.Y + m_bubbleSample.Height / 2);
            float minDistance = float.MaxValue;

            
            for (int i = 0; i < m_board.Length; i++)
            {
                for (int j = 0 ; j < m_board [i].Length; j++)
                {
                    if (m_board[i][j] != null)
                    {
                        float dist = m_board[i][j].DistanceFromCenter(centerPosition);
                        if (dist <= m_bubbleSample.Height - (m_bubbleSample.Height / 8f))
                        {
                            if (row <= i)
                            {
                                rect = m_board[i][j].Rectangle;
                                row = i;
                                col = j;
                                minDistance = dist;
                                stop = true;
                            }
                        }
                    }
                }
            }
            return stop;
        }
    }
}
